Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #3755 Bug Fix for: Shapes and Annotations With yref Parameter Not Drawing on Correct Axis #4177

Merged

Conversation

zfoltz
Copy link
Contributor

@zfoltz zfoltz commented Apr 27, 2023

This PR is related to issue #3755 which, to put more generally, is that shapes and annotations when supplied with a yref parameter are automatically drawn on the primary y-axis despite the supplied yref value. The only way that a shape or annotation can currently be drawn on an axis that differs from the primary axes is when the figure object is created with subplots.make_subplots() with the specs list parameter set to 'secondary_y':True for that specific subplot. In the case of a non-subplotted figure object, it is impossible as the following line:

self.layout[layout_obj][-1].update(xref="x", yref="y")
explicitly sets yref = 'y' when there is not more than one subplot. It is also possible to make a figure object with subplot grid of (1,1) when using plotly express. Using that method there is no way to allow the secondary_y:True to the figure object therefore specifying yref will result in the shape or annotation plotting on the primary y axis regarless of the specified yref and if the secondary_y=True parameter is supplied in the method to add the shape or annotation, that will result in the following error:
if len(refs) == 1 and secondary_y:
raise ValueError(
"""
Cannot add {prop_singular} to secondary y-axis of subplot at position ({r}, {c})
because subplot does not have a secondary y-axis"""
)

This error is also missing it's string.format() method and parameters which are fixed in this as well.

For subplots that use the subplots.make_subplots() with the specs list parameter set to 'secondary_y':True this change does not cause the loss of any functionality it just extends the ability to draw shapes and annotations on additional y-axes when generating figure objects without the subplots.make_subplots() method. Specifying yref and if the secondary_y parameters will not result in an error when used with plotly express (a figure object with a (1,1) subplot grid) or a non subplotted figure object (such as when using go.Figure()), it will just proceed with applying it to the specified yref.

Code PR

  • I have read through the contributing notes and understand the structure of the package. In particular, if my PR modifies code of plotly.graph_objects, my modifications concern the codegen files and not generated files.
  • I have added tests (if submitting a new feature or correcting a bug) or
    modified existing tests.
  • For a new feature, I have added documentation examples in an existing or
    new tutorial notebook (please see the doc checklist as well).
  • I have added a CHANGELOG entry if fixing/changing/adding anything substantial.
  • For a new feature or a change in behaviour, I have updated the relevant docstrings in the code to describe the feature or behaviour (please see the doc checklist as well).

Copy link
Collaborator

@alexcjohnson alexcjohnson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zfoltz this looks great - thanks!! Very nice tests 🎉
💃 Will merge once the tests pass.

@alexcjohnson
Copy link
Collaborator

Hmm the doc build test looks like it might point to a bug here?

import plotly.express as px

df = px.data.iris()
fig = px.scatter(df, x="sepal_length", y="sepal_width", facet_col="species")
# sources of images
sources = [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Iris_setosa_var._setosa_%282595031014%29.jpg/360px-Iris_setosa_var._setosa_%282595031014%29.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Iris_versicolor_quebec_1.jpg/320px-Iris_versicolor_quebec_1.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Iris_virginica_2.jpg/480px-Iris_virginica_2.jpg",
]
# add images
for col, src in enumerate(sources):
    fig.add_layout_image(
        row=1,
        col=col + 1,
        source=src,
        xref="x domain",
        yref="y domain",
        x=1,
        y=1,
        xanchor="right",
        yanchor="top",
        sizex=0.2,
        sizey=0.2,
    )

fig.show()

Gives the error:

ValueError:
    Invalid value of type 'builtins.str' received for the 'yref' property of layout.image
        Received value: 'y domain domain'

@alexcjohnson
Copy link
Collaborator

Also we should add a changelog entry for this fix 🎉

@zfoltz
Copy link
Contributor Author

zfoltz commented May 8, 2023

Yes, you are right! From the looks of it, I think there is a pretty straightforward fix, I'll try to verify and update later today. Thanks for taking a look at this!

@zfoltz
Copy link
Contributor Author

zfoltz commented May 10, 2023

The bug showing in the build-doc is fixed and I updated changelog! 👍

  • I have added a CHANGELOG entry if fixing/changing/adding anything substantial.

@endursa
Copy link

endursa commented May 11, 2023

I tried the bugfix, and while the hline is now working if set to yref=y3 for example, an annotation_text is not displayed at all!

@zfoltz
Copy link
Contributor Author

zfoltz commented May 12, 2023

Hey @endursa, can you post what you are working with? I tried to reproduce the error you mentioned but the following test I ran is working correctly:

import plotly.graph_objects as go

fig = go.Figure()
fig.add_traces(go.Scatter(x=[1,2,3],y=[1,2,3], yaxis='y',name='y1 trace'))
fig.add_traces(go.Scatter(x=[1,2,3],y=[3,2,1], yaxis='y2',name='y2 trace'))
fig.add_traces(go.Scatter(x=[2,3,4],y=[4,4,3], yaxis='y3',name='y3 trace'))

fig.add_hline(y=3, yref='y3', label={'text':'hline annotation text on y3'})
fig.add_hrect(y0=3.5, y1=4, annotation_text='hrect annotation text on y3', yref='y3')

fig.update_layout(
    xaxis=dict(domain=[0, 0.9]),
    yaxis=dict(title="yaxis1 title"),
    yaxis2=dict(title="yaxis2 title",overlaying="y",side="right",position=0.9,),
    yaxis3=dict(title="yaxis3 title",overlaying="y",side="right",position=1,)
)
fig.show()

This results in the following plot:
image

So maybe I'm misunderstanding what you mean by an annotation_text or it could be something else causing the bug. Can you please post a MWE or provide details on how you generate the non-displayed annotation-text? Is it a subplotted figure object or non-subplotted figure object? Is the annotation text supposed to be on an hline,hrect,layout_image,etc.? I'm happy to investigate but just can't seem to reproduce it.

@endursa
Copy link

endursa commented May 12, 2023

yes of course, my problem looks like this:
import plotly
import plotly.graph_objects as go

fig = go.Figure()
fig.add_traces(go.Scatter(x=[1,2,3],y=[-0.009,-0.008,-0.01], yaxis='y',name='y1 trace'))
fig.add_traces(go.Scatter(x=[2,3,4],y=[-50,-52,-58], yaxis='y3',name='y3 trace'))

fig.add_hline(y=-58., 
              line_color='darkblue',
              yref = 'y3',
              line_dash='dot',
              annotation_text='annotation goes here (y3)',
              annotation_position='bottom right'
              )
fig.add_hline(y=-52., 
              line_color='darkblue',
              yref = 'y3',
              line_dash='dot',
              annotation_text='annotation goes here (y3)',
              annotation_position='bottom right'
              )    

fig.update_layout(
    xaxis=dict(
        title='some x axis',
        domain=[0.04,0.92],
        range=['2023-1-1','2024-1-1']),    
    yaxis=dict(
        title='some y axis',
        range=[-0.017,-0.0068],
        gridcolor='lightgrey'
    ),
    yaxis3=dict(
        title='another y axis',
        anchor='x',
        overlaying='y',
        side='right',
        tickmode='array',
        tickvals=[-52,-55,-58],
        range=[-60,-21],
    ),
)
plotly.offline.plot(fig,filename='plot_temp.html')

If one changes the yaxis range to

Figure 2023-05-12 095508

range=[-60,-0.0068]
Figure 2023-05-12 095556

the annotation and another hline appear

sideinfo:
I am running Python 3.10.9

@zfoltz
Copy link
Contributor Author

zfoltz commented May 12, 2023

Thanks for pointing this out and the code @endursa! Looks like I've got it figured out. It appears that when using the annotation_text parameter, the add_annotation function is called without passing the supplied yref see https://github.com/plotly/plotly.py/blob/fc3ef002c7a7898b9244b1549757a64e8df266dd/packages/python/plotly/plotly/basedatatypes.py#L4031-4037
So what is happening is the annotation is showing on the primary y and your shape is showing on the yaxis corresponding to the yref given. Interestingly, adding annotation text by passing text values in the label parameter such as:
label={'text':'my custom annotation'}
actually operates differently under the hood and therefore does not result in this bug. Regardless, I added the necessary yref kwarg to the offending function call and now it's working properly.

@endursa
Copy link

endursa commented May 15, 2023

Thank you very much!
As far as my use case is concerned, it works now as expected :)

@zfoltz
Copy link
Contributor Author

zfoltz commented May 23, 2023

Hey @alexcjohnson anything else needed before merge?

CHANGELOG.md Outdated Show resolved Hide resolved
@alexcjohnson
Copy link
Collaborator

Thanks for the nudge @zfoltz - I made a couple of minor changes, unfortunately I expect the tests will now fail not because of any issues here, but due to the same issue as in #4217 that I'm still trying to track down. Regardless, we'll get this into the next release.

@alexcjohnson alexcjohnson merged commit 42266b0 into plotly:master Jun 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants